﻿using System;
using System.Collections.Generic;
using System.Text;
using NExt.Handler.Base.Default;
using System.IO;
using NExt.Handler.Base.Common.Manager;
using System.Reflection;
using System.Web;

namespace NExt.Handler.Component.Default.JsSerialize
{
    internal class NExtObjectContentBuilder : IContentBuilder
    {
        private readonly INExtScriptGenerator nextScriptGenerator;
        private const string COMMENT_SCRIPT_FORMAT = "/********************************* {0} *********************************/";

        public NExtObjectContentBuilder(INExtScriptGenerator nextObjectContentBuilder)
        {
            this.nextScriptGenerator = nextObjectContentBuilder;
        }

        public string Build(HttpContext context)
        {
            string assemblyName = Path.GetFileNameWithoutExtension(context.Request.Path);
            Assembly assembly = AssemblyManager.GetAssembly(assemblyName);

            string content = BuildContent(assembly);
            //TODO: Assembly bazında cache...
            return content;
        }

        private string BuildContent(Assembly assembly)
        {
            Type[] types = assembly.GetTypes();
            StringBuilder stringBuilder = new StringBuilder();

            string namespaceDeclaration = BuildNamespaceDeclaration(assembly);
            string variableDeclaration = BuildVariableDeclaration(assembly);

            stringBuilder.AppendLine(string.Format(COMMENT_SCRIPT_FORMAT, "Namespace Declaration"));
            stringBuilder.AppendLine(namespaceDeclaration);
            stringBuilder.AppendLine();
            stringBuilder.AppendLine();
            stringBuilder.AppendLine();

            stringBuilder.AppendLine(string.Format(COMMENT_SCRIPT_FORMAT, "Variable Declaration"));
            stringBuilder.AppendLine(variableDeclaration);
            stringBuilder.AppendLine();
            stringBuilder.AppendLine();
            stringBuilder.AppendLine();

            foreach (Type type in types)
            {
                if (!IsAppropriate(type))
                    continue;

                string typeContent = nextScriptGenerator.Build(type);
                typeContent = typeContent.Trim();
                typeContent = typeContent.Trim('\r', '\n');

                stringBuilder.AppendLine(string.Format(COMMENT_SCRIPT_FORMAT, type.FullName));
                stringBuilder.AppendLine(typeContent);
                stringBuilder.AppendLine();
                stringBuilder.AppendLine();
                stringBuilder.AppendLine();
            }

            string content = stringBuilder.ToString();
            return content;
        }

        private string BuildVariableDeclaration(Assembly assembly)
        {
            string assemblyJsName = AssemblyManager.GetJsName(assembly);
            string assemblyFullName = assembly.ManifestModule.Name;
            string assemblyVariableDeclaration = string.Format("{0}='{1}';", assemblyJsName, assemblyFullName);

            return assemblyVariableDeclaration;
        }

        private string BuildNamespaceDeclaration(Assembly assembly)
        {
            const string NAMESPACE_JS_DECLARATION_FORMAT = "Ext.ns('{0}');";
            List<string> namespaceList = new List<string>();
            namespaceList.Add(assembly.GetName().Name);

            foreach (Type type in assembly.GetTypes())
            {
                if (!IsAppropriate(type))
                    continue;

                string typeNamespace = type.Namespace;
                if (namespaceList.Contains(typeNamespace))
                    continue;

                namespaceList.Add(typeNamespace);
            }
            namespaceList.Sort();

            List<string> jsNamespaceList = new List<string>();
            foreach (string @namespace in namespaceList)
            {
                foreach (string namespaceVariation in GetNamespaceVariations(@namespace))
                {
                    if (jsNamespaceList.Contains(namespaceVariation))
                        continue;

                    jsNamespaceList.Add(namespaceVariation);
                }
            }
            jsNamespaceList.Sort();

            StringBuilder stringBuilder = new StringBuilder();
            foreach (string jsNamespace in jsNamespaceList)
            {
                string namespaceJsString = string.Format(NAMESPACE_JS_DECLARATION_FORMAT, jsNamespace);
                stringBuilder.AppendLine(namespaceJsString);
            }

            string content = stringBuilder.ToString();
            return content;
        }

        private IEnumerable<string> GetNamespaceVariations(string @namespace)
        {
            List<string> namespaceVariation = new List<string>();
            string[] parts = @namespace.Split('.');
            string currentNamespace = string.Empty;

            for (int i = 0; i < parts.Length; i++)
            {
                string seperator = i == 0 ? string.Empty : ".";
                currentNamespace = string.Format("{0}{1}{2}", currentNamespace, seperator, parts[i]);
                namespaceVariation.Add(currentNamespace);
            }

            return namespaceVariation;
        }

        private bool IsAppropriate(Type type)
        {
            if (!type.IsPublic)
                return false;
            
            if (type.IsGenericType)
                return false;

            //if (type.IsInterface)
            //    return false;

            if (type.IsAbstract)
                return false;

            //if (!typeof(NExt.Core.Component).IsAssignableFrom(type))
            //    return false;

            if (!typeof(NExt.Core.Model.Base.IJsObject).IsAssignableFrom(type))
                return false;

            return true;
        }
    }
}
